/**
 *  Copyright 2002-2009 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package foo.bar.wiki.domain;

import foo.bar.wiki.services.SetupService;
import foo.bar.wiki.web.filter.FooBarWikiServletContextListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.subject.Subject;

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.util.Date;
import java.util.Map;

import foo.bar.wiki.security.UserAccountRealm;
import org.springframework.web.context.support.WebApplicationContextUtils;

/**
 * A JPA listener that "fill in" the creation and modification info for all domnain objects that
 * implements :-
 * <ul>
 *  <li>{@link foo.bar.wiki.domain.CreationInfoPersistable}</li>
 *  <li>{@link foo.bar.wiki.domain.ModificationInfoPersistable}</li>
 * </ul>
 *
 * @author tmjee
 * @version $Date$ $Id$
 */
public class EntityAuditListener {

    /*
 * @PrePersist
 * @PostPersist
 * @PreUpdate
 * @PostUpdate
 * @PreRemove
 * @PostRemove
 * @PostLoad
 */


    private static final Log LOG = LogFactory.getLog(EntityAuditListener.class);


    public EntityAuditListener() {}

    @PrePersist
    public void prePersist(Object entity) {
        updateEntityAuditInfo(entity);
    }

    @PreUpdate
    public void preUpdate(Object entity) {
        updateEntityAuditInfo(entity);
    }


    protected void updateEntityAuditInfo(Object entity) {
        if (SetupService.ready) {
            if (entity instanceof Identifiable) {
                Identifiable identifiable = (Identifiable) entity;
                if (identifiable.getId() == null) {
                    if (identifiable instanceof CreationInfoPersistable) {
                        if ((currentUser() != null) && (currentUser() != UserAccountRealm.ANONYMOUS_USER_ACCOUNT.getUser())) {
                            ((CreationInfoPersistable)identifiable).setCreator(currentUser());
                        }
                        ((CreationInfoPersistable)identifiable).setCreationDate(new Date(System.currentTimeMillis()));
                    }
                }
                else {
                    if (identifiable instanceof ModificationInfoPersistable) {
                        if ((currentUser() != null) && (currentUser() != UserAccountRealm.ANONYMOUS_USER_ACCOUNT.getUser())) {
                            ((ModificationInfoPersistable)identifiable).setModifier(currentUser());
                        }
                        ((ModificationInfoPersistable)identifiable).setModificationDate(new Date(System.currentTimeMillis()));
                    }
                }
            }
        }
        else {
            LOG.debug("SetupService not ready (still starting up) not doing entity update");
        }
    }


    protected User currentUser() {
        ensureSecurityManagerExists();
        Subject subject = SecurityUtils.getSubject();
        if (subject != null) {
            Object principal = subject.getPrincipal();
            if ((principal != null) && (!UserAccountRealm.ANONYMOUS_USER_ACCOUNT.getUser().equals(principal))) {
                return (User)principal;
            }
        }
        return null;
    }


    protected void ensureSecurityManagerExists() {
        // NOTE:
        // We try to get it from SecurityUtils, if it is not there (not from a http request, hence no Shiro filter
        // interception resulting in SecurityManager being in ThreadContext, we will look it up from Spring's
        // application context. This should only happened for non-http request based invocation.
        org.apache.shiro.mgt.SecurityManager securityManager;
        try {
            securityManager = SecurityUtils.getSecurityManager();
        }
        catch(UnavailableSecurityManagerException e) {
            Map<String, org.apache.shiro.mgt.SecurityManager> instances = (Map<String, org.apache.shiro.mgt.SecurityManager>) WebApplicationContextUtils.getRequiredWebApplicationContext(FooBarWikiServletContextListener.servletContext()).getBeansOfType(org.apache.shiro.mgt.SecurityManager.class);
            if (instances != null && instances.size() > 0) {
                securityManager = instances.values().iterator().next();
                SecurityUtils.setSecurityManager(securityManager);
            }
            else {
                LOG.error("unable to find a org.apache.shiro.mgt.SecurityManager in Spring context");
                throw e;
            }
        }
    }
}
